package ex3.matchmaker;

import java.rmi.Naming;
import java.rmi.RemoteException;
import java.util.PriorityQueue;
import java.util.List;
import java.util.LinkedList;
import ex3.taskqueue.TaskQueue;
import java.util.HashMap;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * Contains a set of executors sharing a common capability, sorted by their natural order.
 * (all operations take O(logn))
 * @author rmn
 */
public class ExecutorsContainer {
	private PriorityQueue<ExecutorData> queue;
	private Character capability;
	private List<String> waiting;
	private static Integer repPort;
	// we use the following hashmap for speedup (to save the frequent lookUps)
	private static HashMap<String, TaskQueue> taskQueues = new HashMap<String, TaskQueue>();
	private static ReentrantReadWriteLock taskQueuesLock = new ReentrantReadWriteLock();
	
	/**
	 * Constructs a new Container of Executors.
	 * @param capability a shared capability.
	 */
	public ExecutorsContainer (Character capability) {
		this.queue = new PriorityQueue<ExecutorData>();
		this.capability = capability;
		this.waiting = new LinkedList<String>();
	}
	
	/**
	 * Static function to <i>init</i> the repository port, for the TaskQueue lookups
	 * taking place later on.
	 * @param repositoryPort the port the repository is running on.
	 */
	public static void setRepPort (int repositoryPort) {
		repPort = repositoryPort;
	}
	
	/**
	 * Adds an Executor to the container. If there are any TaskQueues waiting for that
	 * kind of capability, they are awakened.
	 * @param exec the Executor to be added.
	 * @throws RemoteException
	 */
	public synchronized void addExecutor (ExecutorData exec) throws RemoteException {
		queue.add(exec);
		if (waiting.size() > 0) {
			for (String tq : waiting)
				getTaskQueue(tq).WakeupOp(capability);
			waiting.clear();
		}
	}
	
	/**
	 * Returns an executor ready for use. Once returned it is set as BUSY.
	 * If no such executor is available (all are taken/do not support the capability)
	 * then null is returned and the asking TaskQueue is added to the waiting list to be
	 * notified later on.
	 * @param askingTaskQueue bind name of the TaskQueue that has asked for the executor.
	 * @return a bind name of an available executor, null if there isn't one.
	 */
	public synchronized String getExecutor (String askingTaskQueue) {
		while (queue.size() > 0) {
			ExecutorData exc = queue.poll();
			if (exc.testAndSet(capability))
				return exc.getName();
		}
		// if we got here, then no executor could be given
		waiting.add(askingTaskQueue);
		return null;
	}
	
	/**
	 * Used to look up the TaskQueue bind names and convert them to references to the remote
	 * object. All related data structures and functions are static to ensure maximum sharing
	 * of this data, for speed up. Once a bind name has been looked up, it is stored in a
	 * hash map to save further look ups on the same name.
	 * @param bindName the bind name of the required TaskQueue.
	 * @return the remote object corresponding to the bind name.
	 */
	private static TaskQueue getTaskQueue (String bindName) {
		TaskQueue tq;
		taskQueuesLock.readLock().lock();
		try {
			tq = taskQueues.get(bindName);
		}
		finally {
			taskQueuesLock.readLock().unlock();
		}
		if (tq == null)
			try {
				tq = (TaskQueue)Naming.lookup("//:" + repPort.toString() + "/" + bindName);
			    if (tq == null)
					throw new Exception("Not in repository..");
			    taskQueuesLock.writeLock().lock();
			    try {
			    	taskQueues.put(bindName, tq);
			    }
			    finally {
			    	taskQueuesLock.writeLock().unlock();
			    }
			} catch (Exception e) { 
	            System.out.println("Matchmaker failed to find TaskQueue " +bindName +": " + e.getMessage());
	            System.out.println("Exiting."); 
	            e.printStackTrace(); 
	            System.exit(1);
	        } 
		return tq;
	}

}
